use std::collections::HashMap;

use common_base::{once_cell::sync::Lazy, prelude::*, serde_json};

use crate::cluster::EaiServer;

// maybe not necessary ?
static GLOBAL_PARAM_OBJECT: Lazy<HashMap<String, String>> = Lazy::new(|| HashMap::default());

#[derive(Debug, Default, serde::Serialize, serde::Deserialize)]
pub struct ParamObject {
    pub(crate) eai_server: Option<String>,
    pub(crate) param_root: HashMap<String, String>,
    pub(crate) env_root: HashMap<String, String>,
    // do not support json custom deserialize yet
    // pub(crate) json_type_limit: HashMap<String, String>,
    // connection in param_object for what?
    // pub(crate) auto_connection: bool,
}

impl ParamObject {
    pub fn deserizlize_from_json(mut param_object_json: serde_json::Value) -> Result<Self> {
        // don't deserizlize `eai_server` value, this is for loadbalance use
        // let eai_server = param_object_json
        //     .get("EAIServer")
        //     .and_then(|v| v.as_str())
        //     .ok_or(ErrorCode::RequestInvalid(
        //         "can not find EAIServer, EAIServe must be String Value",
        //     ))?
        //     .trim()
        //     .to_owned();

        // let json_type_limit = match param_object_json.get("jsonTypeLimit") {
        //     Some(serde_json::Value::Null) => HashMap::default(),
        //     Some(serde_json::Value::Object(o)) => {
        //         if o.is_empty() {
        //             HashMap::default()
        //         } else {
        //             todo!()
        //         }
        //     }
        //     Some(v @ _) => {
        //         return Err(ErrorCode::RequestInvalid(format!(
        //             "jsonTypeLimit format error, should be Null/Object, but {}",
        //             v
        //         )))
        //     }
        //     None => HashMap::default(),
        // };

        let param_root = match param_object_json.get("ParamRoot") {
            Some(serde_json::Value::Object(o)) => {
                let mut param_root = HashMap::with_capacity(o.len());
                for i in o {
                    let str_v = match i.1 {
                        serde_json::Value::Null => "".to_owned(),
                        serde_json::Value::Bool(b) => b.to_string(),
                        serde_json::Value::Number(n) => n.to_string(),
                        serde_json::Value::String(s) => s.trim().to_owned(),
                        _ => return Err(ErrorCode::RequestInvalid(format!("ParamRoot format error, {}'s value should be Null/Bool/Number/String, but {}",i.0,i.1))),
                    };
                    param_root.insert(i.0.to_owned(), str_v);
                }
                param_root
            }
            Some(serde_json::Value::Null) | None => HashMap::default(),
            _ => {
                return Err(ErrorCode::RequestInvalid(
                    "ParamRoot format error, ParamRoot should be Null or Object, but not",
                ))
            }
        };

        let env_root = match param_object_json.get_mut("EnvRoot") {
            Some(v) => serde_json::from_value::<HashMap<String, String>>(v.take())?,
            _ => HashMap::default(),
        };

        Ok(Self {
            eai_server: None,
            param_root,
            env_root,
            // json_type_limit,
            // auto_connection: true,
        })
    }

    pub fn serializes_into_bincode(&self) -> Result<Vec<u8>> {
        common_base::bincode::serialize(self).map_err(|e| ErrorCode::from(e))
    }
}

impl ParamObject {
    pub fn new() -> ParamObject {
        let param_object = ParamObject::default();

        // TODO:

        // if "Middle".eq(EAI.Tier) {
        //     param_object.auto_connection = false;
        // }

        // set security/offline from EAI

        param_object
    }

    pub fn set_value_by_param_name(&mut self, key: &str, value: String) {
        self.param_root.insert(key.to_owned(), value);
    }

    pub fn remove_by_param_name(&mut self, key: &str) {
        self.param_root.remove(key);
    }

    pub fn get_value_by_param_name(&self, key: &str) -> &str {
        self.get_value_by_param_name_or_default(key, "")
    }

    pub fn get_value_by_param_name_or_default<'a>(
        &'a self,
        key: &str,
        default: &'a str,
    ) -> &'a str {
        self.param_root
            .get(key)
            .map(|s| s.as_str())
            .unwrap_or(default)
    }

    pub fn set_value_by_env_name(&mut self, key: &str, value: String) {
        self.env_root.insert(key.to_owned(), value);
    }

    pub fn remove_by_env_name(&mut self, key: &str) {
        self.env_root.remove(key);
    }

    pub fn get_value_by_env_name(&self, key: &str) -> &str {
        self.get_value_by_env_name_or_default(key, "")
    }

    pub fn get_value_by_env_name_or_default<'a>(&'a self, key: &str, default: &'a str) -> &'a str {
        self.env_root
            .get(key)
            .map(|s| s.as_str())
            .or_else(|| {
                // TODO: from DBConfigManager.getValue
                // TODO: from EAI.getEnv
                // TODO: from BIZMetaData(Default)
                warn!("can not support find value in DbConfigManager&EAI&BizMetaData(Default) for now.");
                None
            })
            .unwrap_or(default)
    }

    pub fn get_int_by_param_name(&self, key: &str, default: i32) -> i32 {
        self.get_value_by_param_name(key)
            .parse::<i32>()
            .unwrap_or(default)
    }

    pub fn get_bool_by_param_name(&self, key: &str, default: bool) -> bool {
        self.get_value_by_param_name(key)
            .parse::<bool>()
            .unwrap_or(default)
    }
}

impl ParamObject {
    // pub fn get_json_type_limit(&self) -> &HashMap<String, String> {
    //     &self.json_type_limit
    // }

    // pub fn get_eai_server(&self) -> &str {
    //     &self.eai_server
    // }

    pub fn set_eai_server_then_refresh_db_param(&mut self, eai_server: &str) -> Result<()> {
        let eai_server = eai_server.trim();

        self.eai_server = Some(eai_server.to_owned());
        if let Some(eai_server) = EaiServer::get_eai_server(eai_server) {
            self.set_value_by_env_name("DataBaseName", eai_server.db_name.clone());
            self.set_value_by_env_name("DBNO", eai_server.db_no.clone());
            return Ok(());
        } else {
            Err(ErrorCode::EaiServerNotFound(format!(
                "can not find EaiServer for {}",
                eai_server,
            )))
        }
    }
}

impl ParamObject {
    pub fn set_global_value() {
        // TODO: set global_param_object at start time
        todo!()
    }

    pub fn get_global_value_or_default<'a>(key: &str, default: &'a str) -> &'a str {
        GLOBAL_PARAM_OBJECT
            .get(key)
            .map(|v| v.as_str())
            .unwrap_or(default)
    }
}
